package com.googlecode.contests_repo.core.users;


import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;

import com.googlecode.contests_repo.core.config.ConfigurationManager;

import eu.vahlas.json.schema.JSONSchema;
import eu.vahlas.json.schema.JSONSchemaProvider;
import eu.vahlas.json.schema.impl.JacksonSchemaProvider;

/**
 * Controller with methods related to user management.
 * 
 * @author deni, joro
 */
public class UserManager {

	/**
	 * Saves the given user in a file with the specified name. Please note, that
	 * the password is not saved in plain-text. Instead, its SHA-256 hash code
	 * is saved.
	 * 
	 * @param user
	 *            the user to save
	 * @param filename
	 *            the name of the file where the user should be saved.
	 * 
	 * @throws JsonGenerationException
	 * @throws JsonMappingException
	 * @throws IOException
	 * @throws NoSuchAlgorithmException
	 */
	public static void saveUser(User user, String filename) throws JsonGenerationException, JsonMappingException, IOException, NoSuchAlgorithmException {
		ObjectMapper mapper = new ObjectMapper();
		String hashedPassword = getHash(user.getPassword());
		user.setPassword(hashedPassword);
		
		mapper.writeValue(new File(filename), user);
	}
	
	/**
	 * Saves the given user in the default location (<users-dir>/<username>/user.json).
	 * 
	 * @param user
	 * 		the user to save
	 * @throws JsonGenerationException
	 * @throws JsonMappingException
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public static void saveUser(User user) throws JsonGenerationException, JsonMappingException, NoSuchAlgorithmException, IOException {
		String userFolderName = ConfigurationManager.getInstance().getUsersDirectory()
		+ File.separator + user.getLogin();
		File userFolder = new File(userFolderName);
		userFolder.mkdir();
		String filename = userFolder + File.separator + "user.json";
		saveUser(user, filename);
	}
	
	/**
	 * Gets the SHA-256 hash code of the given password.
	 * 
	 * @param password
	 * 		the password whose hash code should be returned.
	 * @return
	 * 		the SHA-256 hash code of the given password
	 * 
	 * @throws NoSuchAlgorithmException
	 * @throws UnsupportedEncodingException
	 */
	public static String getHash(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
	       MessageDigest digest = MessageDigest.getInstance("SHA-256");
	       digest.reset();
	       byte[] bytes = digest.digest(password.getBytes("UTF-8"));

	       StringBuffer hexString = new StringBuffer();
	       for (int i=0; i < bytes.length; i++) {
	    	   hexString.append(Integer.toHexString(0xFF & bytes[i]));
	       }
	  
	       return hexString.toString();
	 }
	
	/**
	 * Checks whether the given file contains valid user data.
	 * Please note, that the method expects a file in valid JSON format
	 * and uses user-schema.json for the validation.
	 * 
	 * @param file
	 * 		the file to validate
	 * @return
	 * 		a list of all validation errors
	 * @throws IOException
	 */
	public static List<String> validateUserBySchema(File file) throws IOException {	
		List<String> errors = null;
		
		// Jackson parsing API: the ObjectMapper can be provided
		// and configured differently depending on the application
		ObjectMapper mapper = new ObjectMapper();
		
		// Allows to retrieve a JSONSchema object on various sources
		// supported by the ObjectMapper provided
		JSONSchemaProvider schemaProvider = new JacksonSchemaProvider(mapper);

		// Retrieves a JSON Schema object based on a file
		InputStream schemaIS = new FileInputStream("user-schema.json");
		JSONSchema schema = schemaProvider.getSchema(schemaIS);

		// Validates a JSON Instance object stored in a file
		InputStream instanceIS = new FileInputStream(file);
		errors = schema.validate(instanceIS);
		instanceIS.close();
		return errors;
	}
	
	/**
	 * Checks whether the given file contains valid user data, i.e.
	 * the file in valid JSON format, conforms to user-schema.json.
	 * If expectedUsername parameter is not null, also check the login 
	 * property in the file is the same.
	 * 
	 * @param userJsonFile
	 * 		the file to validate
	 * @param expectedUsername 
	 * 		expected username or null
	 * @return
	 * 		a list of all validation errors
	 * @throws JsonMappingException 
	 * @throws IOException
	 */
	public static List<String> validateUser(File userJsonFile, String expectedUsername) throws JsonMappingException, IOException {
		List<String> errors = new ArrayList<String>();
		
		// check user.json exists
		if (!userJsonFile.exists()) {
			errors.add("Could not find user.json");
			return errors;
		}
		
		// check user.json is a valid json file 
		// stored in a folder named as the user's login 
		ObjectMapper mapper = new ObjectMapper();
		try {
			User user = mapper.readValue(userJsonFile, User.class);
			if (expectedUsername != null && !expectedUsername.equals(user.getLogin())) {
				errors.add("Login was expected to be "
						+ expectedUsername + ", but was " + user.getLogin() + ".");
			}
		} catch(JsonParseException e) { 
			errors.add("Syntax error: user.json is not a valid JSON file.");
			return errors;
		}
		
		// check against user-schema
		List<String> schemaErrors = validateUserBySchema(userJsonFile);
		errors.addAll(schemaErrors);
		
		return errors;
	}
	
	/**
	 * Validates all users in the repository with the given root and
	 * outputs the result in the console.
	 * 
	 * @param repoRoot
	 * 		the root of the file system of the repository
	 * @throws IOException
	 */
	public static void validateAllUsers(String repoRoot) throws IOException {
		File usersDir = new File(repoRoot + File.separator + "souls" + File.separator + "users");
		if (!usersDir.exists() || !usersDir.isDirectory()) {
			System.out.println("Could not find users directory");
			return;
		}
		
		File[] userDirs = usersDir.listFiles();
		for (File dir : userDirs) {
			if (dir.isDirectory()) {
				String username = dir.getName();
				System.out.println("Validating " + username + "...");
				
				List<String> errors = validateUser(new File(dir, "user.json"), username);
				if (errors.isEmpty()) {
					System.out.println(username + " is a valid user.");
				} else {
					for (String error : errors) {
						System.out.println(error);
					}
				}
				
				System.out.println();
			}
		}
	}
	
	/**
	 * Checks whether a user with the specified login already exists.
	 * 
	 * @param login
	 * 		the login of the user
	 * @return
	 * 		true if a user with the given login name already exists, false otherwise
	 */
	public static boolean isLoginTaken(String login) {
		String filename = ConfigurationManager.getInstance().getUsersDirectory()
			+ File.separator + login + File.separator + "user.json";
		File userJsonFile = new File(filename);
		return userJsonFile.exists();
	}
}
